home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Group Library 1996 July / C-C++ Users Group Library July 1996.iso / vol_400 / 423_01 / accpost / post.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-08-31  |  27.6 KB  |  1,113 lines

  1. /* Plain Vanilla Posting II
  2.  
  3. Copyright 1990 Plain Vanilla Corporation
  4. P.O. Box 4493, San Diego CA 92164
  5. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  6.  
  7. #include <stdio.h>
  8. #include <ctype.h>
  9. #include <setjmp.h>
  10. #include <string.h>
  11. #include <dos.h>
  12. #include <alloc.h>
  13.  
  14. static char id[] = {0x17,0xFD,0x46,0x0F,0x74,0xB3};
  15. static char s1[] = "Changeable journal name (0=no, 1=yes)";
  16. static int changeable_journal_name = 0;
  17. static char sx[] = "";
  18.  
  19. #define MAXIMUM_NAME_OR_FILESPEC_LENGTH 64
  20. #define MAXIMUM_FILESPEC_LENGTH         64
  21. #define MAXIMUM_NAME_LENGTH             40
  22. #define JOURNAL_NAME_LENGTH             12
  23.  
  24. typedef long amount_type;
  25.  
  26. extern amount_type multiply_10(amount_type);
  27. extern amount_type divide_10(amount_type);
  28. extern amount_type sum(amount_type, amount_type);
  29. extern amount_type negative(amount_type);
  30. extern int remainder_10(amount_type);
  31.  
  32. #define AMOUNT_WIDTH  13
  33.  
  34. #define isdebit(x)  ((x)>0)
  35. #define iscredit(x) ((x)<0)
  36. #define iszero(x)   ((x)==0)
  37. #define zero        0L
  38.  
  39. static char *edit_line_number(unsigned n)
  40. {
  41.   static char s[] = {'x','x','x','x',0};
  42.   int i;
  43.   for (i=3; i>=0; i--, n/=10) s[i] = n%10+'0';
  44.   return s;
  45. }  
  46.  
  47. /* case-insensitive strcmp() */
  48.  
  49. static int ci_strcmp(char *s, char *t)
  50. {
  51.   while (*s!=0 && toupper(*s)==toupper(*t)) {s++; t++;}
  52.   return toupper(*s)-toupper(*t);
  53. }
  54.  
  55. static struct
  56. {
  57.   jmp_buf jump_buffer;
  58.   char *message;
  59.   char *name;
  60.   int level;
  61. } err;
  62.  
  63. struct file
  64. {
  65.   int c;
  66.   FILE *fp;
  67.   char *command_line_argument_pointer;
  68.   unsigned line;
  69.   int indented, new_entry;
  70.   char specs[MAXIMUM_FILESPEC_LENGTH+1];
  71. };
  72.  
  73. static struct file source;
  74.  
  75. static FILE *output;
  76. static char output_specs[MAXIMUM_FILESPEC_LENGTH+1];
  77.  
  78. #define error() setjmp(err.jump_buffer)
  79.  
  80. static void error_message(void)
  81. {
  82.   if (source.specs[0]!=0) fputs(source.specs, stdout);
  83.   if (source.line!=0)
  84.   {
  85.     putchar(':');
  86.     fputs(edit_line_number(source.line), stdout);
  87.   }
  88.   putchar(' ');
  89.   fputs(err.message,stdout);
  90.   if (err.name!=NULL)
  91.   {
  92.     fputs(" -- ", stdout);
  93.     fputs(err.name, stdout);
  94.   }
  95.   putchar('\n');
  96.   err.level = 1;
  97. }
  98.  
  99. static void error_exit(void)
  100. {
  101.   error_message();
  102.   exit(err.level);
  103. }
  104.  
  105. static void raise_error_with_name(char *message, char *name)
  106. {
  107.   err.message = message;
  108.   err.name = name;
  109.   longjmp(err.jump_buffer, 1);
  110. }
  111.  
  112. void raise_error(char *message)
  113. {
  114.   raise_error_with_name(message, NULL);
  115. }
  116.  
  117. static void declare_error_with_name(char *message, char *name)
  118. {
  119.   err.message = message;
  120.   err.name = name;
  121.   error_message();
  122. }
  123.  
  124. static void declare_error(char *message)
  125. {
  126.   declare_error_with_name(message, NULL);
  127. }
  128.  
  129. static void open_source_file(char *specs)
  130. {
  131.   if (specs[0]=='[')
  132.   {
  133.     source.command_line_argument_pointer = specs+1;
  134.     specs = "[]";
  135.   }
  136.   else
  137.   {
  138.     if (specs[0]=='+') specs++;
  139.     source.fp = fopen(specs,"r");
  140.     if (source.fp==NULL)
  141.       raise_error_with_name("Can't open source file", specs);
  142.     source.command_line_argument_pointer = NULL;
  143.   }
  144.   strcpy(source.specs, specs);
  145.   source.c = '\n';
  146.   source.line = 0;
  147. }
  148.  
  149. static void read_character(void)
  150. {
  151.   if (source.c!=EOF)
  152.   {
  153.     if (source.c=='\n' && source.line<9999) source.line++;
  154.     if (source.command_line_argument_pointer==NULL)
  155.       source.c = getc(source.fp);
  156.     else
  157.     {
  158.       source.c = *source.command_line_argument_pointer++;
  159.       if (source.c==0) source.c = EOF;
  160.     }
  161.   }
  162. }
  163.  
  164. static void close_source_file(void)
  165. {
  166.   if (source.command_line_argument_pointer==NULL);
  167.     fclose(source.fp);
  168. }
  169.  
  170. static void invalid_amount(void)
  171. {
  172.   raise_error("Invalid dollar amount");
  173. }
  174.  
  175. static void skip_spaces(void)
  176. {
  177.   while (source.c==' ' || source.c=='\t') read_character();
  178. }
  179.  
  180. static void next_line(void)
  181. {
  182.   source.indented = source.new_entry = 0;
  183.   while (1)
  184.   {
  185.     source.indented = 0;
  186.     while (source.c!='\n')
  187.     {
  188.       if (source.c==EOF) {source.new_entry = 1; return;}
  189.       read_character();
  190.     }
  191.     read_character();
  192.     if (source.c==' ' || source.c=='\t') source.indented = 1;
  193.     skip_spaces();
  194.     if (isalpha(source.c)) break;
  195.     if (source.c=='\n' || source.c==EOF) source.new_entry = 1;
  196.     else if (source.c!='*') declare_error("Questionable journal line");
  197.   }
  198. }
  199.  
  200. static amount_type read_amount(void)
  201. {
  202.   int commas = 0;
  203.   int cents = 0;
  204.   int position = 0;
  205.   int nothing = 1;
  206.   amount_type amount;
  207.   amount = zero;
  208.   skip_spaces();
  209.   while (1)
  210.   {
  211.     if (source.c==',')
  212.     {
  213.       if (nothing || cents || commas && position!=4 || !commas && position>3)
  214.         invalid_amount();
  215.       commas = 1;
  216.       position = 0;
  217.       commas = 1;
  218.     }
  219.     else if (source.c=='.')
  220.     {
  221.       if (nothing || cents || commas && position!=4) invalid_amount();
  222.       cents = 1;
  223.       commas = 0;
  224.       position = 0;
  225.     }
  226.     else if (isdigit(source.c))
  227.     {
  228.       nothing = 0;
  229.       if (commas && position==4 || cents && position==3) invalid_amount();
  230.       amount = sum(multiply_10(amount), (amount_type)(source.c-'0'));
  231.     }
  232.     else break;
  233.     read_character();
  234.     position++;
  235.   }
  236.   if (nothing || cents && position!=3 || commas && position!=4)
  237.     invalid_amount();
  238.   if (!cents)
  239.   {
  240.     amount = multiply_10(amount);
  241.     amount = multiply_10(amount);
  242.   }
  243.   return amount;
  244. }
  245.  
  246.  
  247. static unsigned read_number(void)
  248. {
  249.   int no_number = 1;
  250.   unsigned number = 0;
  251.   skip_spaces();
  252.   while (isdigit(source.c))
  253.   {
  254.     if (number>6552) {number = 0xFFFF; break;}
  255.     number = 10*number + source.c - '0';
  256.     no_number = 0;
  257.     read_character();
  258.   }
  259.   if (no_number) number = 0xFFFF;
  260.   return number;
  261. }
  262.  
  263. static int invalid_file_character(int c)
  264. {
  265.   return c<=' ' || c=='"' || c=='/'  || c=='[' || c==']' ||
  266.          c=='|' || c=='<' || c=='>'  || c=='+' || c=='=' ||
  267.          c==';' || c==',';
  268. }
  269.  
  270. static void read_specs(char *specs)
  271. {
  272.   int i = 0;
  273.   skip_spaces();
  274.   if (source.c=='+')
  275.   {
  276.     specs[i++] = '+';
  277.     read_character();
  278.   }
  279.   if (invalid_file_character(source.c))
  280.     raise_error("Invalid or missing file specifications");
  281.   while (!invalid_file_character(source.c))
  282.   {
  283.     if (i<MAXIMUM_FILESPEC_LENGTH) specs[i++] = source.c;
  284.     read_character();
  285.   }
  286.   specs[i] = 0;
  287.   skip_spaces();
  288. }
  289.  
  290. static void read_name(char *name)
  291. {
  292.   int i = 0;
  293.   skip_spaces();
  294.   if (!isalpha(source.c))
  295.     raise_error("Invalid or missing name");
  296.   do
  297.   {
  298.     do
  299.     {
  300.       if (i<MAXIMUM_NAME_LENGTH) name[i++] = source.c;
  301.       read_character();
  302.     } while (isalnum(source.c) || source.c=='/');
  303.     if (i<MAXIMUM_NAME_LENGTH) name[i++] = ' ';
  304.     skip_spaces();
  305.   } while (isalpha(source.c));
  306.   if (name[i-1]==' ') i--;
  307.   name[i] = 0;
  308. }
  309.  
  310. static void *get_memory(int n)
  311. {
  312.   void *g = malloc((unsigned long)n);
  313.   char *t;
  314.   if (g==NULL)
  315.   {
  316.     puts("\nInsufficient memory");
  317.     exit(1);
  318.   }
  319.   for (t=g; n>0; n--) *t++ = 0;
  320.   return g;
  321. }
  322.  
  323. static void open_output_file(char *specs)
  324. {
  325.   if (specs==NULL) output = stdout;
  326.   else
  327.   {
  328.     if (specs[0]=='+') output = fopen(specs+1,"a");
  329.     else output = fopen(specs,"w");
  330.     if (output==NULL) raise_error_with_name("Can't open output file", specs);
  331.     strcpy(output_specs, specs);
  332.   }
  333. }
  334.  
  335. static void close_output_file(void)
  336. {
  337.   if (output!=stdout && output!=NULL) fclose(output);
  338.   output = NULL;
  339. }
  340.  
  341. static void write_character(int c)
  342. {
  343.   if (output==stdout) putchar(c);
  344.   else if (putc(c,output)==EOF)
  345.   {
  346.     close_output_file();
  347.     raise_error_with_name("File write error", output_specs);
  348.   }
  349. }
  350.  
  351. static void write_string(char *s)
  352. {
  353.   while (*s!=0) write_character(*s++);
  354. }
  355.  
  356. static void write_amount(amount_type amount, int trim)
  357. {
  358.   char s[AMOUNT_WIDTH+1];
  359.   register char *t = s+sizeof(s);
  360.   *--t = 0;
  361.   *--t = remainder_10(amount)+'0'; amount = divide_10(amount);
  362.   *--t = remainder_10(amount)+'0'; amount = divide_10(amount);
  363.   *--t = '.';
  364.   do
  365.   {
  366.     if (t==s+7 || t==s+3) *--t = ',';
  367.     *--t = remainder_10(amount)+'0';
  368.     amount = divide_10(amount);
  369.   } while (!iszero(amount));
  370.   if (trim>=0)
  371.   {
  372.     if (t<s+trim) declare_error("Digit trimmed");  
  373.     while (t>s+trim) *--t = ' ';
  374.   }
  375.   write_string(t);
  376. }
  377.  
  378. static void write_repeated_characters(int character, int trim)
  379. {
  380.   int count;
  381.   for (count=trim; count<AMOUNT_WIDTH; count++) write_character(character);
  382. }
  383.  
  384. typedef struct
  385. {
  386.   unsigned day : 5;
  387.   unsigned month : 4;
  388.   unsigned year : 7;
  389. } packed_date;
  390.  
  391. static void invalid_date(void)
  392. {
  393.   raise_error("Invalid date");
  394. }
  395.  
  396. static unsigned get_date_number(void)
  397. {
  398.   unsigned n = read_number();
  399.   if (n>2049) invalid_date();
  400.   return n;
  401. }
  402.  
  403. static packed_date read_date(void)
  404. {
  405.   packed_date date;
  406.   unsigned day, month, year;
  407.   static int DAYS_IN_MONTH[13] = {0,31,29,31,30,31,30,31,31,30,31,30,31};
  408.   skip_spaces();
  409.   if (isdigit(source.c))
  410.   {
  411.     int delimiter;
  412.     month = get_date_number();
  413.     delimiter = source.c; read_character();
  414.     if (delimiter!='/' && delimiter!='-' && delimiter!='.') invalid_date();
  415.     day = get_date_number();
  416.     if (source.c!=delimiter) invalid_date();
  417.     read_character();
  418.     year = get_date_number();
  419.   }
  420.   else if (isalpha(source.c))
  421.   {
  422.     static char *MONTH_NAME[] =
  423.     {
  424.       "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL",
  425.       "AUG", "SEP", "OCT", "NOV", "DEC"
  426.     };
  427.     char month_name[4];
  428.     int i = 0;
  429.     while (isalpha(source.c) || source.c=='.')
  430.     {
  431.       if (i<3) month_name[i++] = toupper(source.c);
  432.       read_character();
  433.     }
  434.     month_name[i] = 0;
  435.     for (i=0; i<12; i++) if (strcmp(month_name, MONTH_NAME[i])==0) break;
  436.     month = i+1;
  437.     if (source.c=='.') read_character();
  438.     day = get_date_number();
  439.     if (source.c==',') read_character();
  440.     year = get_date_number();
  441.   }
  442.   else invalid_date();
  443.   if (year<50) year += 2000;
  444.   else if (year>79 && year<100) year += 1900;
  445.   else if (year<1980 || year>2049) invalid_date();
  446.   date.year = year-1980;
  447.   if (month==0 || month>12 || day==0 || day>DAYS_IN_MONTH[month] ||
  448.       month==2 && day==29 && year%4!=0)
  449.     invalid_date();
  450.   date.month = month;
  451.   date.day = day;
  452.   return date;
  453. }
  454.  
  455. struct journal
  456. {
  457.   char name[JOURNAL_NAME_LENGTH+1];
  458. };
  459.  
  460. struct ledger_line
  461. {
  462.   struct ledger_line *next;
  463.   packed_date date;
  464.   struct journal journal;
  465.   unsigned line;
  466.   amount_type amount;
  467. };
  468.  
  469. enum table_type {ACCOUNT, AMOUNT};
  470.  
  471. struct table
  472. {
  473.   struct table *next;
  474.   enum table_type type;
  475.   union
  476.   {
  477.     struct
  478.     {
  479.       struct ledger_line *last_line;
  480.       struct ledger_line *first_line;
  481.     } account;
  482.     amount_type amount;
  483.   } x;
  484.   char name[MAXIMUM_NAME_LENGTH+1];
  485. };
  486.  
  487. static struct table *first_table_entry = NULL;
  488. static struct table *last_table_entry;
  489.  
  490. static char company_name[MAXIMUM_NAME_LENGTH+1];
  491. static char name[MAXIMUM_NAME_OR_FILESPEC_LENGTH+1];
  492. static struct journal current_journal;
  493. static packed_date current_date;
  494. static amount_type balance;
  495. static amount_type total;
  496.  
  497. static void write_two_digits(unsigned n)
  498. {
  499.   write_character(n/10+'0');
  500.   write_character(n%10+'0');
  501. }
  502.  
  503. static void write_name(char *s)
  504. {
  505.   int i, j;
  506.   for (i=0; i<MAXIMUM_NAME_LENGTH+1; i++)
  507.     write_character(*s==0 ? ' ' : *s++);
  508. }
  509.  
  510. static amount_type account_balance(struct table *p)
  511. {
  512.   amount_type amount;
  513.   if (p->type==ACCOUNT)
  514.   {
  515.     struct ledger_line *q;
  516.     amount = zero;
  517.     for (q=p->x.account.first_line; q!=NULL; q=q->next)
  518.       amount = sum(amount, q->amount);
  519.   }
  520.   else amount = p->x.amount;
  521.   return amount;
  522. }
  523.  
  524. static struct table *look_for_table_entry(char *name)
  525. {
  526.   struct table *p;
  527.   for (p=first_table_entry; p!=NULL; p=p->next)
  528.     if (ci_strcmp(name,p->name)==0) break;
  529.   return p;
  530. }
  531.  
  532. static struct table *find_table_entry(char *name)
  533. {
  534.   struct table *p = look_for_table_entry(name);
  535.   if (p==NULL)
  536.     raise_error_with_name("Undefined ledger account or non-ledger amount",
  537.                           name);
  538.   return p;
  539. }
  540.  
  541. static struct table *new_table_entry(char *name)
  542. {
  543.   struct table *p;
  544.   p = look_for_table_entry(name);
  545.   if (p!=NULL)
  546.     raise_error_with_name("Duplicate ledger account or non-ledger amount name",
  547.                           name);
  548.   p = get_memory(sizeof(struct table)-MAXIMUM_NAME_LENGTH+strlen(name));
  549.   if (first_table_entry==NULL) first_table_entry = p;
  550.   else last_table_entry->next = p;
  551.   last_table_entry = p;
  552.   strcpy(p->name, name);
  553.   return p;
  554. }
  555.  
  556. static void write_date(packed_date date)
  557. {
  558.   write_two_digits(date.month);
  559.   write_character('/');
  560.   write_two_digits(date.day);
  561.   write_character('/');
  562.   write_two_digits((date.year+80)%100);
  563. }
  564.  
  565. static void write_extended_date(packed_date date)
  566. {
  567.   static char *MONTH_NAME[] =
  568.   {
  569.     "? ", "January ", "February ", "March ", "April ", "May ", "June ",
  570.     "July ", "August ", "September ", "October ", "November ", "December"
  571.   };
  572.   write_string(MONTH_NAME[date.month]);
  573.   if (date.day>9) write_character(date.day/10+'0');
  574.   write_character(date.day%10+'0');
  575.   write_string(date.year<2000-1980 ? ", 19" : ", 20");
  576.   write_two_digits((date.year+80)%100);
  577. }  
  578.  
  579. static void post(amount_type amount, struct table *p)
  580. {
  581.   struct ledger_line *q;
  582.   if (p->type==AMOUNT)
  583.     raise_error_with_name("Attempt to post to non-ledger amount", p->name);
  584.   balance = sum(balance, amount);
  585.   q = get_memory(sizeof(struct ledger_line));
  586.   q->date = current_date;
  587.   q->journal = current_journal;
  588.   q->line = source.line;
  589.   q->amount = amount;
  590.   if (p->x.account.first_line==NULL) p->x.account.first_line = q;
  591.   else p->x.account.last_line->next = q;
  592.   p->x.account.last_line = q;
  593. }
  594.  
  595. static void check_company_name(char *name)
  596. {
  597.   if (company_name[0]==0) strcpy(company_name,name);
  598.   else if (ci_strcmp(name,company_name)!=0)
  599.     raise_error("Company name mismatch");
  600. }
  601.  
  602. static void company_name_required(void)
  603. {
  604.   if (company_name[0]==0)
  605.     declare_error("Company name not previously recorded");
  606. }
  607.  
  608. static void read_ledger(char *specs)
  609. {
  610.   struct file save_source;
  611.   save_source = source;
  612.   if (error())
  613.   {
  614.     error_message();
  615.     if (source.fp!=NULL) close(source.fp);
  616.     source = save_source;
  617.     return;
  618.   }
  619.   open_source_file(specs);
  620.   read_character();
  621.   read_name(name);
  622.   check_company_name(name);
  623.   while (1)
  624.   {
  625.     struct table *p;
  626.     while (source.c==' ' || source.c=='\t' || source.c=='\n') read_character();
  627.     if (source.c==EOF) break;
  628.     read_name(name);
  629.     p = new_table_entry(name);
  630.     p->type = ACCOUNT;
  631.     while (1)
  632.     {
  633.       struct ledger_line *q;
  634.       int credit = 0;
  635.       while (source.c==' ' || source.c=='\t' || source.c=='\n')
  636.         read_character();
  637.       if (isalpha(source.c) || source.c==EOF) break;
  638.       q = get_memory(sizeof(struct ledger_line));
  639.       q->date = read_date();
  640.       {
  641.         int i;
  642.         read_name(name);
  643.         for (i=0; i<JOURNAL_NAME_LENGTH; i++)
  644.           q->journal.name[i] = name[i];
  645.       }
  646.       q->line = read_number();
  647.       if (q->line>9999) raise_error("Invalid ledger line");
  648.       skip_spaces();
  649.       if (source.c=='(') {credit = 1; read_character();}
  650.       q->amount = read_amount();
  651.       if (credit)
  652.       {
  653.         if (source.c!=')') raise_error("Invalid ledger line");
  654.         q->amount = negative(q->amount);
  655.         read_character();
  656.       }
  657.       if (p->x.account.first_line==NULL) p->x.account.first_line = q;
  658.       else p->x.account.last_line->next = q;
  659.       p->x.account.last_line = q;
  660.       balance = sum(balance, q->amount);
  661.     }
  662.   }
  663.   close_source_file();
  664.   if (!iszero(balance))
  665.     raise_error("Books are out of balance");
  666.   source = save_source;
  667. }
  668.  
  669. static void write_ledger(char *specs)
  670. {
  671.   struct table *p;
  672.   open_output_file(specs);
  673.   if (error())
  674.   {
  675.     error_message();
  676.     close_output_file();
  677.     return;
  678.   } 
  679.   write_string(company_name); write_character('\n');
  680.   for (p=first_table_entry; p!=NULL; p=p->next) if (p->type==ACCOUNT)
  681.   {
  682.     struct ledger_line *q;
  683.     write_character('\n');
  684.     write_string(p->name);
  685.     write_character('\n');
  686.     for (q=p->x.account.first_line; q!=NULL; q=q->next)
  687.     {
  688.       int i;
  689.       unsigned n;
  690.       int credit = 0;
  691.       char *t;
  692.       amount_type amount = q->amount;
  693.       write_string("  ");
  694.       write_date(q->date);
  695.       write_character(' ');
  696.       for (i=0, t=q->journal.name; i<JOURNAL_NAME_LENGTH; i++)
  697.         write_character(*t!=0 ? *t++ : ' ');
  698.       write_character(' ');
  699.       write_string(edit_line_number(q->line));
  700.       write_character(' ');
  701.       if (iscredit(amount))
  702.       {
  703.         credit = 1;
  704.         write_character('(');
  705.         amount = negative(amount);
  706.       }
  707.       else write_character(' ');
  708.       write_amount(amount,0);
  709.       if (credit) write_character(')');
  710.       write_character('\n');
  711.     }
  712.   }
  713.   close_output_file();
  714. }
  715.  
  716. static void scan_away_insertion(void)
  717. {
  718.   while (source.c!='}' && source.c!='\n' && source.c!=EOF) read_character();
  719.   if (source.c=='}') read_character();
  720. }
  721.  
  722. static void report(char *specs1, char *specs2, int trim)
  723. {
  724.   struct file save_source;
  725.   open_output_file(specs2);
  726.   save_source = source;
  727.   if (error())
  728.   {
  729.     error_message();
  730.     if (specs1!=NULL) close_source_file();
  731.     source = save_source;
  732.     close_output_file();
  733.     return;
  734.   }
  735.   if (specs1!=NULL)
  736.   {
  737.     open_source_file(specs1);
  738.     read_character();
  739.   }
  740.   while (source.c!=EOF && !(specs1==NULL && source.c=='\n') && output!=NULL)
  741.   {
  742.     amount_type amount;
  743.     int parentheses = 0;
  744.     if (error())
  745.     {
  746.       error_message();
  747.       if (specs1!=NULL) close_source_file();
  748.       source = save_source;
  749.       close_output_file();
  750.       return;
  751.     }
  752.     while (source.c!='{' && source.c!=EOF && !(specs1==NULL && source.c=='\n'))
  753.     {
  754.       write_character(source.c);
  755.       read_character();
  756.     }
  757.     if (source.c==EOF || specs1==NULL && source.c=='\n') break;
  758.     read_character();
  759.     if (error())
  760.     {
  761.       error_message();
  762.       scan_away_insertion();
  763.       continue;
  764.     }
  765.     skip_spaces();
  766.     if (source.c=='}' || source.c=='-' || source.c=='=')
  767.     {
  768.       int i;
  769.       write_character(' ');
  770.       write_repeated_characters(source.c=='}' ? ' ' : source.c, trim);
  771.       write_character(' ');
  772.       scan_away_insertion();
  773.       continue;
  774.     }
  775.     read_name(name);
  776.     if (source.c==':')
  777.     {
  778.       if (ci_strcmp(name,"COMPANY")==0) write_string(company_name);
  779.       else if (ci_strcmp(name,"DATE")==0) write_extended_date(current_date);
  780.       else raise_error("Invalid \"blank\"");
  781.       scan_away_insertion();
  782.       continue;
  783.     }
  784.     amount = account_balance(find_table_entry(name));
  785.     if (source.c!=',') raise_error("Missing dr/cr");
  786.     read_character();
  787.     read_name(name);
  788.     if (ci_strcmp(name,"DR")==0)
  789.     {
  790.       if (iscredit(amount))
  791.       {
  792.         parentheses = 1;
  793.         amount = negative(amount);
  794.       }
  795.     }
  796.     else if (ci_strcmp(name,"CR")==0)
  797.     {
  798.       if (!isdebit(amount)) amount = negative(amount);
  799.       else parentheses = 1;
  800.     }
  801.     else raise_error("Missing cr/dr");
  802.     if (source.c!='}') raise_error("Missing right brace");
  803.     read_character();
  804.     write_character(parentheses ? '(' : ' ');
  805.     write_amount(amount, trim);
  806.     write_character(parentheses ? ')' : ' ');
  807.   }
  808.   close_output_file();
  809.   if (specs1!=NULL)
  810.   {
  811.     close_source_file();
  812.     source = save_source;
  813.   }
  814. }
  815.  
  816. static void journal(char *specs)
  817. {
  818.   struct file save_source;
  819.   struct journal save_journal;
  820.   save_source = source;
  821.   save_journal = current_journal;
  822.   balance = zero;
  823.   total = zero;
  824.   if (error())
  825.   {
  826.     error_message();
  827.     if (source.fp!=NULL) close(source.fp);
  828.     source = save_source;
  829.     current_journal = save_journal;
  830.     return;
  831.   }
  832.   open_source_file(specs);
  833.   current_journal.name[0] = 0;
  834.   while (1)
  835.   {
  836.     next_line();
  837.     if (source.new_entry)
  838.     {
  839.       if (balance!=0)
  840.       {
  841.         declare_error("Unbalanced entry");
  842.         balance = 0;
  843.       }
  844.       if (total!=0)
  845.       {
  846.         declare_error("Unfinished total");
  847.         total = 0;
  848.       }
  849.     }
  850.     if (source.c==EOF) break;
  851.     if (error()) {error_message(); continue;}
  852.     read_name(name);
  853.     if (source.c==':')
  854.     {
  855.       read_character();
  856.       if (ci_strcmp(name,"JOURNAL")==0)
  857.       {
  858.         int i;
  859.         skip_spaces();
  860.         read_name(name);
  861.         name[JOURNAL_NAME_LENGTH] = 0;
  862.         if (name[JOURNAL_NAME_LENGTH-1]==' ')
  863.           name[JOURNAL_NAME_LENGTH-1] = 0;
  864.         if (!changeable_journal_name && current_journal.name[0]!=0 &&
  865.             ci_strcmp(current_journal.name,name)!=0)
  866.           declare_error_with_name("Journal name changed within file", name);
  867.         strcpy(current_journal.name, name);
  868.       }
  869.       else if (ci_strcmp(name,"DATE")==0)
  870.         current_date = read_date();
  871.       else if (ci_strcmp(name,"COMPANY")==0)
  872.       {
  873.         read_name(name);
  874.         check_company_name(name);
  875.       }
  876.       else if (ci_strcmp(name,"CLOSE")==0 ||
  877.                ci_strcmp(name,"ADD")==0)
  878.       {
  879.         struct table *p, *q;
  880.         int close = ci_strcmp(name,"CLOSE")==0;
  881.         read_name(name);
  882.         p = find_table_entry(name);
  883.         if (source.c=='.')
  884.         {
  885.           read_character();
  886.           if (source.c!='.') declare_error("Incomplete ellipsis");
  887.           while (source.c=='.') read_character();
  888.           read_name(name);
  889.           q = find_table_entry(name);
  890.           if (p->type!=ACCOUNT || q->type!=ACCOUNT)
  891.             raise_error("Series of non-ledger amounts");
  892.         }
  893.         else q = p;
  894.         while (1)
  895.         {
  896.           if (close) post(negative(account_balance(p)), p);
  897.           else total = sum(total, account_balance(p));
  898.           if (p==q) break;
  899.           p = p->next;
  900.           if (p==NULL) raise_error("Invalid group of ledger accounts");
  901.         }
  902.       }
  903.       else if (ci_strcmp(name,"INTO")==0)
  904.       {
  905.         read_name(name);
  906.         post(negative(balance), find_table_entry(name));
  907.       }
  908.       else if (ci_strcmp(name,"SUBTRACT")==0)
  909.       {
  910.         read_name(name);
  911.         total = sum(total, negative(account_balance(find_table_entry(name))));
  912.       }
  913.       else if (ci_strcmp(name,"DEBIT")==0)
  914.       {
  915.         total = sum(total, read_amount());
  916.       }
  917.       else if (ci_strcmp(name,"CREDIT")==0)
  918.       {
  919.         total = sum(total, negative(read_amount()));
  920.       }
  921.       else if (ci_strcmp(name,"MESSAGE")==0)
  922.       {
  923.         skip_spaces();
  924.         report(NULL,NULL,-1);
  925.         putchar('\n');
  926.       }
  927.       else if (ci_strcmp(name,"INCLUDE")==0)
  928.       {
  929.         company_name_required();
  930.         read_specs(name);
  931.         journal(name);
  932.       }
  933.       else if (ci_strcmp(name,"READ LEDGER")==0)
  934.       {
  935.         company_name_required();
  936.         read_specs(name);
  937.         read_ledger(name);
  938.       }
  939.       else if (ci_strcmp(name,"WRITE LEDGER")==0)
  940.       {
  941.         company_name_required();
  942.         read_specs(name);
  943.         write_ledger(name);
  944.       }
  945.       else if (ci_strcmp(name,"REPORT")==0)
  946.       {
  947.         static char report_file[MAXIMUM_FILESPEC_LENGTH+1];
  948.         int trim = 0;
  949.         company_name_required();
  950.         read_specs(name);
  951.         if (source.c!=',') raise_error("Missing report file");
  952.         read_character();
  953.         read_specs(report_file);
  954.         if (source.c==',')
  955.         {
  956.           read_character();
  957.           trim = read_number();
  958.           if (trim<0 || trim>AMOUNT_WIDTH-4)
  959.             declare_error("Invalid trimming value");
  960.         }
  961.         report(name, report_file, trim);
  962.       }
  963.       else if (ci_strcmp(name,"TOTAL")==0)
  964.       {
  965.         struct table *p;
  966.         read_name(name);
  967.         p = look_for_table_entry(name);
  968.         if (p==NULL) p = new_table_entry(name);
  969.         p->type = AMOUNT;
  970.         p->x.amount = total;
  971.         total = 0;
  972.       }
  973.       else if (ci_strcmp(name,"TRIAL BALANCE")==0 ||
  974.                ci_strcmp(name,"CONDENSED TRIAL BALANCE")==0)
  975.       {
  976.         int condensed = ci_strcmp(name,"CONDENSED TRIAL BALANCE")==0;
  977.         struct table *p;
  978.         amount_type debit_balance = 0;
  979.         amount_type credit_balance = 0;
  980.         char trial_balance_journal_name[MAXIMUM_NAME_LENGTH+1];
  981.         company_name_required();
  982.         read_specs(name);
  983.         if (source.c==',')
  984.         {
  985.           read_character();
  986.           read_name(trial_balance_journal_name);
  987.         }
  988.         else
  989.           trial_balance_journal_name[0] = 0;
  990.         open_output_file(name);
  991.         if (trial_balance_journal_name[0]!=0)
  992.         {
  993.           write_string("Journal: ");
  994.           write_string(
  995.           trial_balance_journal_name);
  996.           write_string("\n");
  997.         }
  998.         write_string("Company: ");
  999.         write_string(company_name);
  1000.         write_string("\nDate: ");
  1001.         write_date(current_date);
  1002.         write_string("\n\n");
  1003.         for (p=first_table_entry; p!=NULL; p=p->next) if (p->type==ACCOUNT)
  1004.         {
  1005.           int credit = 0;
  1006.           amount_type balance = account_balance(p);
  1007.           if (iscredit(balance))
  1008.           {
  1009.             write_character(' ');
  1010.             credit = 1;
  1011.             balance = negative(balance);
  1012.             credit_balance = sum(credit_balance, balance);
  1013.           }
  1014.           else
  1015.             debit_balance = sum(debit_balance, balance);
  1016.           if (!iszero(balance) || !condensed)
  1017.           {
  1018.             write_name(p->name);
  1019.             if (credit)
  1020.             {
  1021.               write_character(' ');
  1022.               write_repeated_characters(' ',0);
  1023.             }
  1024.             write_amount(balance, 0);
  1025.             write_character('\n');
  1026.           }
  1027.         }
  1028.         write_name("*");
  1029.         write_repeated_characters('-',0);
  1030.         write_string("  ");
  1031.         write_repeated_characters('-',0);
  1032.         write_character('\n');
  1033.         write_name("* Totals");
  1034.         write_amount(debit_balance,0);
  1035.         write_string("  ");
  1036.         write_amount(credit_balance,0);
  1037.         write_character('\n');
  1038.         write_name("*");
  1039.         write_repeated_characters('=',0);
  1040.         write_string("  ");
  1041.         write_repeated_characters('=',0);
  1042.         write_character('\n');
  1043.         close_output_file();
  1044.       }
  1045.       else raise_error_with_name("Invalid command", name);
  1046.     }
  1047.     else
  1048.     {
  1049.       amount_type amount = read_amount();
  1050.       if (source.indented) amount = negative(amount);
  1051.       post(amount, find_table_entry(name));
  1052.     }
  1053.     skip_spaces();
  1054.     if (source.c!='*' && source.c!='\n' && source.c!=EOF)
  1055.       declare_error("Spurious or superfluous information on command line");
  1056.   }
  1057.   close_source_file();
  1058.   source = save_source;
  1059.   current_journal = save_journal;
  1060. }
  1061.  
  1062.  
  1063. void main(int argc, char **argv)
  1064. {
  1065.   if (argc<2) declare_error("Missing journal file specifications");
  1066.   else 
  1067.   {
  1068.     static char comline[129];
  1069.     /* get date from DOS */
  1070.     {
  1071.       struct date d;
  1072.       getdate(&d);
  1073.       current_date.month = d.da_mon;
  1074.       current_date.day = d.da_day;
  1075.       current_date.year = d.da_year-1980;
  1076.       if (current_date.year>2049-1980) current_date.year = 2049-1980;
  1077.     }
  1078.     /* reassmeble command line */
  1079.     {
  1080.       int i;
  1081.       char *s = comline;
  1082.       for (i=1; i<argc; i++)
  1083.       {
  1084.         char *t = argv[i];
  1085.         if (s!=comline) *s++ = ' ';
  1086.         while (*t!=0) *s++ = *t++;
  1087.       }
  1088.       *s = 0;
  1089.     }
  1090.     /* scan reassembled command line */
  1091.     {
  1092.       char *s = comline;
  1093.       while (*s!=0)
  1094.       {
  1095.         char *ss;
  1096.         while (*s==' ' || *s=='\t') s++;
  1097.         ss = s;
  1098.         if (*s=='[')
  1099.         {
  1100.           while (*s!=']' && *s!=0) s++;
  1101.         }
  1102.         else if (*s!=0)
  1103.         {
  1104.           while (*s!=' ' && *s!='\t' && *s!=0) s++;
  1105.         }
  1106.         if (*s!=0) *s++ = 0;
  1107.         journal(ss);
  1108.       }
  1109.     }
  1110.   }
  1111.   exit(err.level);
  1112. }
  1113.